/*
 * jQuery Snoop Plug-in
 *
 * @requires jQuery version 1.3+
 * @author Lucas Arruda <lucasarruda@live.com>
 * @extends jQuery
 * @thanks Manoel Carvalho (manoelhc@gmail.com)
 * 
 * @license Copyright 2010 Lucas Nascimento Arruda, Manoel Carvalho
 * 
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.*
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */

// jQuery Snoop JSON Object
var jQuerySnoop = {
	// Pool for hidden fields and their values and procedures
  hiddenPool : [],
  // 300 miliseconds of default interval between each check
  interval   : 300,
  thread     : null,

  /*
   * Implementation of heap()
   * Queues the events related to the hidden fields in a pool.
   * @param object (Object) : The selector's object
   * @param procedure (Function) : The procedure to be executed
   */
  heap : function (object, procedure) {   
    var element = jQuery(object.selector);
    // Check if the element exists and if it's an input of type hidden
    if (element.length > 0 && element.is(':input[type=hidden]')) {
      // Empty holes in the pool can be reused with this flag that
      // check if there's a hole in the pool and if it was filled
      var holeFilled = false;
      // If the pool is not empty and a hole is found we fill it up.
      if (this.hiddenPool.length != 0) { 
        jQuery.each(this.hiddenPool, function(i,o) { 
          if (o == null) {
            jQuerySnoop.hiddenPool[i] = { selector  : object.selector,
                                          value     : object.val(),
                                          procedure : procedure
                                        };
            holeFilled = true;
            return false;
          }			
        });
      }
      // No holes filled means the pool is empty/full so we add a new position
      if (!holeFilled) {
        this.hiddenPool.push({ selector  : object.selector, 
                               value     : object.val(),
                               procedure : procedure
                             });
      }
      // Start the listener if it is stopped
      if (this.thread == null) {
        this.listen();
      }
    }
  },

  /*
   * Implementation of destroy()
   * Deletes the entries of a specific hidden field from the pool.
   * @param object (Object) : The object to be unlistened.
   */
  destroy : function (object) {
    jQuery.each(this.hiddenPool, function(i,o) {
      // If the position on the queue is not null and matches the object selector
      // we set it as null.
      if (o != null) {
        if (object.selector == o.selector) {
          jQuerySnoop.hiddenPool[i] = null;
        }
      } 
    });
  },

  /*
   * Implementation of setIntervalTime()
   * Sets the time to wait between each check thread.
   * @param miliseconds (int) : The time in miliseconds between each verification
   */
  setIntervalTime : function (miliseconds) {
    this.interval = miliseconds;
  },

  /*
   * Implementation of check()
   * Checks if the hidden field still exists and executes the related procedure
   * if the value has been changed during the last check.
   */
  check : function () { 
    // Flag used to stop the thread if the pool is empty
    var emptyPool = true;
    // Go through each position checking their stored and actual values
    jQuery.each(jQuerySnoop.hiddenPool, function(i,o) {
      if (o != null) {
        var element = jQuery(o.selector);
        if (element.length > 0) {
          if (o.value != element.val()) {
            o.procedure();
            o.value = element.val();
          }
          if (emptyPool) {
            // The pool is not empty because at least one element was found in the DOM
            emptyPool = false;
          }
        }
        else {
          // If the element was not found delete it from the pool
          jQuerySnoop.hiddenPool[i] = null;
        }
      }
    });
    // Once the pool is empty stops thread and re-start 
    // the pool to ensure better performance on site.
    if (emptyPool) {
      clearInterval(jQuerySnoop.thread);
      jQuerySnoop.thread = null;
      jQuerySnoop.hiddenPool = [];
    }
  },

  /*
   * Implementation of listen()
   * Creates the listener thread.
   */
  listen : function() {
    this.thread = window.setInterval(jQuerySnoop.check, this.interval);
  }
};
// End of Snoop JSON Object

// Verify if jQuery is loaded
if (typeof(jQuery) != 'function') {
  alert('jQuerySnoop: jQuery is not properly loaded.');
} 
else {
  var ver = jQuery.fn.jquery.split('.');
  // Verify if jQuery's version is newer than 1.3
  if (ver[0] < 1 || ver[1] < 3) {
    alert("jQuerySnoop: jQuery incompatible version.\nPlease use jQuery 1.3+");
  }
  else {
    // Create a jQuery function
    jQuery.fn.snoop = function(procedure) {
      // HEAP: The param submitted by the user needs to be a function
      if (typeof(procedure) == 'function') {
        jQuerySnoop.heap(this, procedure);
      }
      else {
        // DESTROY: A simple 'destroy' string
        if (procedure == 'destroy') {
          jQuerySnoop.destroy(this);
        }
      }
    };
    // User is able to specify the interval time between each check (Default is 300 miliseconds)
    jQuery.fn.snoopInterval = function(miliseconds) {
      if (typeof(interval) == 'number') {
        jQuerySnoop.setIntervalTime(miliseconds);
      }
    };
    // onValueChange alias
    jQuery.fn.onValueChange = function(procedure) {
      jQuery(this.selector).snoop(procedure);
    };
  }
};